﻿// <copyright file="SlidingButton.cs" company="Ucaya">
// </copyright>
// <author>Ucaya</author>

namespace KinectExtensions.Controls
{
    #region

    using System;
    using System.ComponentModel.Composition;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Controls.Primitives;
    using System.Windows.Data;

    using KinectExtensions.Interactivity;
    using KinectExtensions.Nui;
    using KinectExtensions.Services;

    using Microsoft.Research.Kinect.Nui;

    #endregion

    /// <summary>
    ///   TODO: Update summary.
    /// </summary>
    public class SlidingButton : ButtonBase
    {
        #region Constants and Fields

        /// <summary>
        ///   IsKinectOver depedency property.
        /// </summary>
        public static readonly DependencyProperty IsKinectOverProperty = DependencyProperty.Register("IsKinectOver", typeof(bool), typeof(SlidingButton), new PropertyMetadata((d, e) => ((SlidingButton)d).OnIsKinectOverChanged((bool)e.OldValue, (bool)e.NewValue)));

        /// <summary>
        ///   IsRightHand depedency property.
        /// </summary>
        public static readonly DependencyProperty IsRightHandProperty = DependencyProperty.Register("IsRightHand", typeof(bool), typeof(SlidingButton), new PropertyMetadata((d, e) => ((SlidingButton)d).OnIsRightHandChanged((bool)e.OldValue, (bool)e.NewValue)));

        /// <summary>
        ///   OpenToTheRight depedency property.
        /// </summary>
        public static readonly DependencyProperty OpenToTheRightProperty = DependencyProperty.Register("OpenToTheRight", typeof(bool), typeof(SlidingButton), new PropertyMetadata((d, e) => ((SlidingButton)d).OnOpenToTheRightChanged((bool)e.OldValue, (bool)e.NewValue)));

        /// <summary>
        ///   SlidingPosition depedency property.
        /// </summary>
        public static readonly DependencyProperty SlidingPositionProperty = DependencyProperty.Register("SlidingPosition", typeof(double), typeof(SlidingButton), new PropertyMetadata((d, e) => ((SlidingButton)d).OnSlidingPositionChanged((double)e.OldValue, (double)e.NewValue)));

        /// <summary>
        ///   The inverse converter.
        /// </summary>
        private readonly SlidingValueConverter inverseConverter;

        /// <summary>
        ///   The over trigger.
        /// </summary>
        private readonly KinectOverTrigger overTrigger;

        /// <summary>
        ///   The last master.
        /// </summary>
        private HandPosition lastMaster;

        /// <summary>
        ///   The last zone.
        /// </summary>
        private MagneticKinectZone lastZone;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes static members of the <see cref = "SlidingButton" /> class.
        /// </summary>
        static SlidingButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SlidingButton), new FrameworkPropertyMetadata(typeof(SlidingButton)));
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "SlidingButton" /> class.
        /// </summary>
        public SlidingButton()
        {
            ServiceLocator.Instance.SatisfyImportsOnce(this);

            this.overTrigger = new KinectOverTrigger();
            this.inverseConverter = new SlidingValueConverter();

            this.SetBinding(IsKinectOverProperty, new Binding(this.overTrigger.GetPropertyName(_ => _.IsKinectOver)) { Source = this.overTrigger });
            this.SetBinding(SlidingPositionProperty, new Binding(this.overTrigger.GetPropertyName(_ => _.OverPosition) + ".X") { Source = this.overTrigger, Converter = this.inverseConverter });

            this.Loaded += this.SlidingButton_Loaded;
            this.Unloaded += this.SlidingButton_Unloaded;
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets HandTrackingService.
        /// </summary>
        [Import]
        public HandTrackingService HandTrackingService { get; set; }

        /// <summary>
        ///   Gets or sets a value indicating whether IsKinectOver.
        /// </summary>
        public bool IsKinectOver
        {
            get
            {
                return (bool)this.GetValue(IsKinectOverProperty);
            }

            set
            {
                this.SetValue(IsKinectOverProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets a value indicating whether IsRightHand.
        /// </summary>
        public bool IsRightHand
        {
            get
            {
                return (bool)this.GetValue(IsRightHandProperty);
            }

            set
            {
                this.SetValue(IsRightHandProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets a value indicating whether OpenToTheRight.
        /// </summary>
        public bool OpenToTheRight
        {
            get
            {
                return (bool)this.GetValue(OpenToTheRightProperty);
            }

            set
            {
                this.SetValue(OpenToTheRightProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets SlidingPosition.
        /// </summary>
        public double SlidingPosition
        {
            get
            {
                return (double)this.GetValue(SlidingPositionProperty);
            }

            set
            {
                this.SetValue(SlidingPositionProperty, value);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        ///   The on apply template.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.RefreshOpeningSideState();
            this.RefreshIsOverState();
        }

        #endregion

        #region Methods

        /// <summary>
        ///   handles the IsKinectOverProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnIsKinectOverChanged(bool oldValue, bool newValue)
        {
            this.RefreshIsOverState();

            HandPosition master = this.HandTrackingService.HandMaster;
            this.IsRightHand = master != null && master.JointId == JointID.HandRight;

            if (this.lastMaster != null)
            {
                if (this.lastZone == this.lastMaster.KinectZone)
                {
                    this.lastMaster.KinectZone = null;
                }
            }

            if (this.lastZone != null)
            {
                Action a = () => this.lastZone.Leave();
                a.Delay(100);
            }

            this.lastMaster = master;
            if (newValue)
            {
                Window h = Application.Current.MainWindow;
                if (h == null)
                {
                    return;
                }

                this.lastZone = new MagneticKinectZone();
                this.lastZone.HideOnCapture = true;

                Point center = this.TransformToVisual(h).Transform(new Point(this.ActualWidth / 2, this.ActualHeight / 2));
                this.lastZone.MagneticCenter = center;

                this.lastZone.Enter();

                this.lastMaster.KinectZone = this.lastZone;
            }
        }

        /// <summary>
        ///   handles the IsRightHandProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnIsRightHandChanged(bool oldValue, bool newValue)
        {
            this.RefreshOpeningSideState();
        }

        /// <summary>
        ///   handles the OpenToTheRightProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnOpenToTheRightChanged(bool oldValue, bool newValue)
        {
            this.inverseConverter.Inverse = newValue;
            this.RefreshOpeningSideState();
        }

        /// <summary>
        ///   handles the SlidingPositionProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnSlidingPositionChanged(double oldValue, double newValue)
        {
            if (newValue >= 1)
            {
                this.OnClick();
            }
        }

        /// <summary>
        ///   The refresh is over state.
        /// </summary>
        private void RefreshIsOverState()
        {
            VisualStateManager.GoToState(this, this.IsKinectOver ? "OpenedState" : "ClosedState", true);
        }

        /// <summary>
        ///   The refresh opening side state.
        /// </summary>
        private void RefreshOpeningSideState()
        {
            string handSide = "LeftHand";
            if (this.IsRightHand)
            {
                handSide = "RightHand";
            }

            VisualStateManager.GoToState(this, this.OpenToTheRight ? "OpenToTheRight" + handSide : "OpenToTheLeft" + handSide, true);
        }

        /// <summary>
        ///   The sliding button_ loaded.
        /// </summary>
        /// <param name = "sender">
        ///   The sender.
        /// </param>
        /// <param name = "e">
        ///   The e.
        /// </param>
        private void SlidingButton_Loaded(object sender, RoutedEventArgs e)
        {
            this.overTrigger.Attach(this);
        }

        /// <summary>
        ///   The sliding button_ unloaded.
        /// </summary>
        /// <param name = "sender">
        ///   The sender.
        /// </param>
        /// <param name = "e">
        ///   The e.
        /// </param>
        private void SlidingButton_Unloaded(object sender, RoutedEventArgs e)
        {
            this.overTrigger.Detach();
        }

        #endregion

        /// <summary>
        ///   The sliding value converter.
        /// </summary>
        private class SlidingValueConverter : IValueConverter
        {
            #region Public Properties

            /// <summary>
            ///   Gets or sets a value indicating whether Inverse.
            /// </summary>
            public bool Inverse { get; set; }

            #endregion

            #region Public Methods

            /// <summary>
            ///   The convert.
            /// </summary>
            /// <param name = "value">
            ///   The value.
            /// </param>
            /// <param name = "targetType">
            ///   The target type.
            /// </param>
            /// <param name = "parameter">
            ///   The parameter.
            /// </param>
            /// <param name = "culture">
            ///   The culture.
            /// </param>
            /// <returns>
            ///   The convert.
            /// </returns>
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var val = (double)value;
                return !this.Inverse ? 1 - val : val;
            }

            /// <summary>
            ///   The convert back.
            /// </summary>
            /// <param name = "value">
            ///   The value.
            /// </param>
            /// <param name = "targetType">
            ///   The target type.
            /// </param>
            /// <param name = "parameter">
            ///   The parameter.
            /// </param>
            /// <param name = "culture">
            ///   The culture.
            /// </param>
            /// <returns>
            ///   The convert back.
            /// </returns>
            /// <exception cref = "NotImplementedException">
            /// </exception>
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }

            #endregion
        }
    }
}